package com.after.saas.file.utils;

import lombok.extern.slf4j.Slf4j;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.ClassFinder;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.model.datastorage.migration.VariablePrepare;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.org.apache.poi.util.IOUtils;
import org.docx4j.wml.*;
import org.springframework.util.ResourceUtils;

import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;
import java.io.*;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.beans.PropertyDescriptor;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 * @Classname Doc4jWordUtil
 * @Description TODO
 * @auth after
 * @since 2022/4/9 15:58
 */
@Slf4j
public class Docx4jWordUtil {

    static final String TEMPLATE_NAME = "D:/data/02-docx4j-模板.docx";
    static final String OUT_PATH = "D:/data/01-个人简历-模板.docx";


    public static void addImage() throws Exception {
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();

        File file = new File("D:\\1.jpg");
        byte[] bytes = convertImageToByteArray(file);
        addImageToPackage(wordMLPackage, bytes);

        wordMLPackage.save(new File(TEMPLATE_NAME));

    }

    public static void main(String[] args) throws Exception {
//       convertDocxToPdf(TEMPLATE_NAME, "D:/test-docx4j.pdf");
        HashMap<String, String> mappings = new HashMap<String, String>();

        //构造非循环格子的表格数据

        mappings.put("name", "马参军");

        mappings.put("sex", "男");

        mappings.put("skill", "散谣：三人成虎事多有");
        fillTable(mappings);
    }

    /**
     * docx文档转换为PDF
     *
     * @param body     文档
     * @param response 响应给前端
     * @return pdf 输出流
     * @throws Exception 可能为Docx4JException, FileNotFoundException, IOException等
     */
    public static void convertDocxToPdf(byte[] body, HttpServletResponse response) throws Exception {
        response.setContentType("application/pdf");
        File docxFile = FileUtil.byteToFile(body, UUID.randomUUID().toString() + ".docx");
        try {
            WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(docxFile);
            setFontMapper(mlPackage);
            Docx4J.toPDF(mlPackage, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("docx文档转换为PDF失败");
        }
        FileUtil.deleteTempFile(docxFile);
    }


    /**
     * docx文档转换为PDF
     *
     * @param pdfPath PDF文档存储路径
     * @throws Exception 可能为Docx4JException, FileNotFoundException, IOException等
     */
    public static void convertDocxToPdf(String docxPath, String pdfPath) throws Exception {

        FileOutputStream fileOutputStream = null;
        try {
            File file = new File(docxPath);
            fileOutputStream = new FileOutputStream(new File(pdfPath));
            WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(file);
            setFontMapper(mlPackage);
            Docx4J.toPDF(mlPackage, new FileOutputStream(new File(pdfPath)));
        } catch (Exception e) {
            e.printStackTrace();
            log.error("docx文档转换为PDF失败");
        } finally {
            IOUtils.closeQuietly(fileOutputStream);
        }
    }

    private static void setFontMapper(WordprocessingMLPackage mlPackage) throws Exception {
        Mapper fontMapper = new IdentityPlusMapper();
        fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
        fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
        fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
        fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
        fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
        fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
        fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
        fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
        fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
        fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
        fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
        fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
        fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
        fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));

        mlPackage.setFontMapper(fontMapper);
    }

    /**
     * 加载模板并替换数据
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static void replaceData(Map<String, String> data) {
        try {
            InputStream templateInputStream = new FileInputStream(TEMPLATE_NAME);
            //加载模板文件并创建WordprocessingMLPackage对象
            WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
            MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
            VariablePrepare.prepare(wordMLPackage);
            documentPart.variableReplace(data);
            OutputStream os = new FileOutputStream(OUT_PATH);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            wordMLPackage.save(outputStream);
            outputStream.writeTo(os);
            os.close();
            outputStream.close();
            templateInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 如果从数据库查出来的是不同对象集合，那么该方法直接把对象的属性和值直接存为Map
     * 比如示例中可以是两个对象，ClassInfo(className,total,male,female)、student(name,sex,...)
     * 前提是模板中的占位符一定要和对象的属性名一一对应
     *
     * @param objlist
     * @return
     * @throws Exception
     */
    public HashMap<String, String> toMap(List<Object> objlist) throws Exception {
        HashMap<String, String> variables = new HashMap<>();
        String value = "";
        if (objlist == null && objlist.isEmpty()) {
            return null;
        } else {
            for (Object obj : objlist) {
                Field[] fields = null;
                fields = obj.getClass().getDeclaredFields();
                //删除字段数组里的serialVersionUID
                for (int i = 0; i < fields.length; i++) {
                    fields[i].setAccessible(true);
                    if (fields[i].getName().equals("serialVersionUID")) {
                        fields[i] = fields[fields.length - 1];
                        fields = Arrays.copyOf(fields, fields.length - 1);
                        break;
                    }
                }
                //遍历数组，获取属性名及属性值
                for (Field field : fields) {
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    //如果属性类型是Date
                    if (field.getGenericType().toString().equals("class java.util.Date")) {
                        PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
                        Method getMethod = pd.getReadMethod();
                        Date date = (Date) getMethod.invoke(obj);
                        value = (date == null) ? "" : new SimpleDateFormat("yyyy-MM-dd").format(date);
                    } else if (field.getGenericType().toString().equals("class java.lang.Integer")) {
                        //如果属性类型是Integer
                        PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
                        Method getMethod = pd.getReadMethod();
                        Object object = getMethod.invoke(obj);
                        value = (object == null) ? String.valueOf(0) : object.toString();

                    } else if (field.getGenericType().toString().equals("class java.lang.Double")) {
                        //如果属性类型是Double
                        PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
                        Method getMethod = pd.getReadMethod();
                        Object object = getMethod.invoke(obj);
                        value = (object == null) ? String.valueOf(0.0) : object.toString();

                    } else {
                        PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
                        Method getMethod = pd.getReadMethod();
                        Object object = getMethod.invoke(obj);
                        value = (object == null) ? "  " : object.toString();

                    }
                    variables.put(fieldName, value);
                }
            }
            return variables;
        }
    }

    /**
     * Docx4j拥有一个由字节数组创建图片部件的工具方法, 随后将其添加到给定的包中. 为了能将图片添加
     * 到一个段落中, 我们需要将图片转换成内联对象. 这也有一个方法, 方法需要文件名提示, 替换文本,
     * 两个id标识符和一个是嵌入还是链接到的指示作为参数.
     * 一个id用于文档中绘图对象不可见的属性, 另一个id用于图片本身不可见的绘制属性. 最后我们将内联
     * 对象添加到段落中并将段落添加到包的主文档部件.
     *
     * @param wordMLPackage 要添加图片的包
     * @param bytes         图片对应的字节数组
     * @throws Exception 不幸的createImageInline方法抛出一个异常(没有更多具体的异常类型)
     */
    private static void addImageToPackage(WordprocessingMLPackage wordMLPackage,
                                          byte[] bytes) throws Exception {
        BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);

        int docPrId = 1;
        int cNvPrId = 2;
        Inline inline = imagePart.createImageInline("Filename hint", "Alternative text", docPrId, cNvPrId, false);

        P paragraph = addInlineImageToParagraph(inline);

        wordMLPackage.getMainDocumentPart().addObject(paragraph);
    }

    /**
     * 创建一个对象工厂并用它创建一个段落和一个可运行块R.
     * 然后将可运行块添加到段落中. 接下来创建一个图画并将其添加到可运行块R中. 最后我们将内联
     * 对象添加到图画中并返回段落对象.
     *
     * @param inline 包含图片的内联对象.
     * @return 包含图片的段落
     */
    private static P addInlineImageToParagraph(Inline inline) {
        // 添加内联对象到一个段落中
        ObjectFactory factory = new ObjectFactory();
        P paragraph = factory.createP();
        R run = factory.createR();
        paragraph.getContent().add(run);
        Drawing drawing = factory.createDrawing();
        run.getContent().add(drawing);
        drawing.getAnchorOrInline().add(inline);
        return paragraph;
    }

    /**
     * 将图片从文件对象转换成字节数组.
     *
     * @param file 将要转换的文件
     * @return 包含图片字节数据的字节数组
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static byte[] convertImageToByteArray(File file)
            throws FileNotFoundException, IOException {
        InputStream is = new FileInputStream(file);
        long length = file.length();
        // 不能使用long类型创建数组, 需要用int类型.
        if (length > Integer.MAX_VALUE) {
            System.out.println("File too large!!");
        }
        byte[] bytes = new byte[(int) length];
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
            offset += numRead;
        }
        // 确认所有的字节都没读取
        if (offset < bytes.length) {
            System.out.println("Could not completely read file " + file.getName());
        }
        is.close();
        return bytes;
    }

    public static void fillTable(Map<String, String> mappings) throws Exception {
        File file = ResourceUtils.getFile("classpath:template/02-docx4j-模板.docx");
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(file);
        //构造循环列表的数据
        ClassFinder find = new ClassFinder(Tbl.class);

        new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), find);

        Tbl table = (Tbl) find.results.get(1);

        Tr dynamicTr = (Tr) table.getContent().get(1);//第二行约定为模板

        String dynamicTrXml = XmlUtils.marshaltoString(dynamicTr);//获取模板行的xml数据
        List<Map<String, Object>> dataList = getDataList();

        for (Map<String, Object> dataMap : dataList) {

            Tr newTr = (Tr) XmlUtils.unmarshallFromTemplate(dynamicTrXml, dataMap);//填充模板行数据

            table.getContent().add(newTr);

        }
        //删除模板行的占位行
        table.getContent().remove(1);
        wordMLPackage.getMainDocumentPart().variableReplace(mappings);//设置全局的变量替换
        Docx4J.save(wordMLPackage, new File(OUT_PATH));

    }


    public static List<Map<String, Object>> getDataList() {

        List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();

        Map<String, Object> m1 = new HashMap<String, Object>();

        m1.put("item.number", "1");
        m1.put("item.name", "关银萍");

        m1.put("item.sex", "女");
        m1.put("item.tip", "来吧，青龙偃月刀");

        dataList.add(m1);

        Map<String, Object> m2 = new HashMap<String, Object>();

        m2.put("item.number", "2");
        m2.put("item.name", "马云禄");

        m2.put("item.sex", "女");
        m2.put("item.tip", "啥玩意，手里方片摸牌，占位占位看到换行的样式效果，占位占位看到换行的样式效果");

        dataList.add(m2);

        Map<String, Object> m3 = new HashMap<String, Object>();

        m3.put("item.number", "3");
        m3.put("item.name", "张星彩");

        m3.put("item.sex", "女");
        m3.put("item.tip", "长缨在手，擦擦擦擦");

        dataList.add(m3);

        return dataList;

    }
}
